package sss

import (
	"fmt"
	"strings"
	"strconv"
)


const Header = "/* DO NOT EDIT: generated from SSS */\n"

type context struct {
	palette  map[string] ColorValue
}
func makeContext(raw_palette ([] ColorDef)) context {
	var palette = make(map[string] ColorValue)
	for _, item := range raw_palette {
		palette[item.Name] = item.Value
	}
	return context { palette }
}

func Output(s StyleSheet) string {
	var ctx = makeContext(s.Palette)
	var buf = new(strings.Builder)
	buf.WriteString(Header)
	for _, rule := range s.Rules {
		buf.WriteString(outputRule(rule, "", ctx))
	}
	return buf.String()
}

func outputRule(r Rule, parent_selector string, ctx context) string {
	var this_selector = outputSelector(r.Selector)
	if parent_selector != "" {
		switch r.Selector.(type) {
		case ClassSelector:
			this_selector = fmt.Sprintf("%s %s", parent_selector, this_selector)
		case PseudoClassSelector:
			this_selector = fmt.Sprintf("%s%s", parent_selector, this_selector)
		}
	}
	var buf strings.Builder
	var styles = outputStyles(r.Styles, ctx)
	buf.WriteString(fmt.Sprintf("%s {\n%s}\n", this_selector, styles))
	for _, item := range r.SubRules {
		buf.WriteString(outputRule(item, this_selector, ctx))
	}
	return buf.String()
}

func outputSelector(s Selector) string {
	switch S := s.(type) {
	case ClassSelector:
		return fmt.Sprintf(".%s", S.Name)
	case PseudoClassSelector:
		if S.Negate {
			return fmt.Sprintf(":not(:%s)", S.Name)
		} else {
			return fmt.Sprintf(":%s", S.Name)
		}
	default:
		panic("impossible branch")
	}
}

func outputStyles(s Styles, ctx context) string {
	var pairs = stylesToPairs(s, ctx)
	return pairs.String()
}

func stylesToPairs(s Styles, ctx context) cssKeyValues {
	var list cssKeyValues
	var g = &(s.Geometry)
	list.Add (
		cssKeyString("position", g.Positioning),
		cssKeyMaybeNumber("z-index", g.LayerIndex),
		cssKeyMeasure("top", g.Top),
		cssKeyMeasure("right", g.Right),
		cssKeyMeasure("bottom", g.Bottom),
		cssKeyMeasure("left", g.Left),
	)
	list.Add (
		cssKeyQuadMeasure("margin", g.Margin),
		cssKeyQuadMeasure("padding", g.Padding),
		cssKeyQuadMeasure("border-width", g.BorderWidth),
	)
	list.Add (
		cssKeyMeasure("width", g.Width),
		cssKeyMeasure("height", g.Height),
		cssKeyMeasure("min-width", g.MinWidth),
		cssKeyMeasure("min-height", g.MinHeight),
		cssKeyMeasure("max-width", g.MaxWidth),
		cssKeyMeasure("max-height", g.MaxHeight),
	)
	list.Add (
		cssKeyMaybeOverflow("overflow", g.Overflow),
	)
	var f = &(s.FancyBox)
	list.Add (
		cssKeyBorderStyle("border-style", f.BorderStyle, g.BorderWidth),
		cssKeyColor("border-color", f.BorderColor, ctx),
		cssKeyBoxShadows("box-shadow", f.BoxShadows, ctx),
		cssKeyMaybeBackground("background", f.Background, ctx),
	)
	var l = &(s.LayoutConfig)
	list.Add (
		cssKeyMaybeNumber("order", l.ItemConfig.Order),
		cssKeyMaybeNumber("flex-grow", l.ItemConfig.FlexGrow),
		cssKeyMaybeNumber("flex-shrink", l.ItemConfig.FlexShrink),
		cssKeyMeasure("flex-basis", l.ItemConfig.FlexBasis),
		cssKeyString("align-self", l.ItemConfig.AlignSelf),
		cssKeyMaybeGridArea("grid-area", l.ItemConfig.GridArea),
	)
	switch c := getLayoutContainerConfig(l.ContainerConfig).(type) {
	case BlockContainer:
		list.Add(cssKeyString("display", "block"))
	case InlineContainer:
		list.Add(cssKeyString("display", "inline"))
	case InlineBlockContainer:
		list.Add(cssKeyString("display", "inline-block"))
	case FlexContainer:
		list.Add (
			cssKeyString("display", "flex"),
			cssKeyString("flex-direction", c.FlexDirection),
			cssKeyString("flex-wrap", c.FlexWrap),
			cssKeyString("justify-content", c.JustifyContent),
			cssKeyString("align-items", c.AlignItems),
			cssKeyString("align-content", c.AlignContent),
			cssKeyMeasure("row-gap", c.RowGap),
			cssKeyMeasure("column-gap", c.ColumnGap),
		)
	case GridContainer:
		list.Add (
			cssKeyString("display", "grid"),
			cssKeyGridTemplateSeq("grid-template-rows", c.TemplateRows),
			cssKeyGridTemplateSeq("grid-template-columns", c.TemplateColumns),
			cssKeyMeasure("row-gap", c.RowGap),
			cssKeyMeasure("column-gap", c.ColumnGap),
		)
	}
	var t = &(s.TextConfig)
	list.Add (
		cssKeyQuotedStringSeq("font-family", t.FontConfig.FontFamily),
		cssKeyMeasure("font-size", t.FontConfig.FontSize),
		cssKeyString("font-weight", t.FontConfig.FontWeight),
		cssKeyString("font-style", t.FontConfig.FontStyle),
	)
	switch w := getTextWrapConfig(t.WrapConfig).(type) {
	case SingleLineConfig:
		list.Add (
			cssKeyString("text-overflow", "ellipsis"),
			cssKeyString("white-space", "nowrap"),
		)
	case MultiLineConfig:
		list.Add (
			cssKeyString("text-overflow", "ellipsis"),
			cssKeyString("white-space", "pre-wrap"),
			cssKeyString("overflow-wrap", "break-word"),
		)
		if w.HyphensEnabled {
			list.Add(cssKeyString("hyphens", "auto"))
		}
	}
	list.Add (
		cssKeyColor("color", t.Color, ctx),
		cssKeyString("text-decoration", t.TextDecoration),
	)
	return list
}
func getLayoutContainerConfig(config MaybeLayoutContainerConfig) LayoutContainerConfig {
	if config != nil {
		var config, _ = config()
		return config
	} else {
		return nil
	}
}
func getTextWrapConfig(config MaybeTextWrapConfig) TextWrapConfig {
	if config != nil {
		var config, _ = config()
		return config
	} else {
		return nil
	}
}

type cssKeyValues ([] cssKeyValue)
func cssKeyValuesCreate(pairs... cssKeyValue) cssKeyValues {
	return cssKeyValues(pairs)
}
func cssKeyValuesCreate1(key string, value string) cssKeyValues {
	return cssKeyValuesCreate(cssKeyValueCreate(key, value))
}
func (list *cssKeyValues) Add(others... cssKeyValues) {
	for _, other := range others {
		*list = append(*list, other...)
	}
}
func (list *cssKeyValues) String() string {
	var buf strings.Builder
	for _, item := range *list {
		buf.WriteString(item.String())
	}
	return buf.String()
}
type cssKeyValue struct {
	Key    string
	Value  string
}
func cssKeyValueCreate(key string, value string) cssKeyValue {
	return cssKeyValue { Key: key, Value: value }
}
func (pair cssKeyValue) String() string {
	return fmt.Sprintf("\t%s: %s;\n", pair.Key, pair.Value)
}

func cssKeyString(key string, value string) cssKeyValues {
	if value != "" {
		return cssKeyValuesCreate1(key, value)
	} else {
		return nil
	}
}
func cssKeyMaybeNumber(key string, number MaybeNumber) cssKeyValues {
	if number == nil { return nil }
	{ var value, set = number()
	if set {
		return cssKeyValuesCreate1(key, fmt.Sprint(value))
	} else {
		return nil
	} }
}
func cssColor(color Color, ctx context) string {
	if !(color.Set) { return "" }
	{ var color, exists = ctx.palette[color.Name]
	if !(exists) { return "" }
	var h, s, l = color.H, color.S, color.L
	var a, has_a = (func() (Number, bool) {
		if color.A == nil { return 0, false }
		return color.A()
	})()
	if has_a {
		return fmt.Sprintf("hsla(%.f, %.f%%, %.f%%, %.f%%)", h, s, l, a)
	} else {
		return fmt.Sprintf("hsl(%.f, %.f%%, %.f%%)", h, s, l)
	} }
}
func cssKeyColor(key string, color Color, ctx context) cssKeyValues {
	return cssKeyString(key, cssColor(color, ctx))
}
func cssMeasure(measure Measure) string {
	if measure.Set {
		return fmt.Sprintf("%s%s", fmt.Sprint(measure.Value), measure.Unit)
	} else {
		return ""
	}
}
func cssKeyMeasure(key string, measure Measure) cssKeyValues {
	return cssKeyString(key, cssMeasure(measure))
}
func cssKeyQuadMeasure(key string, measure QuadMeasure) cssKeyValues {
	var values ([] string)
	var collect = func() cssKeyValues {
		if len(values) > 0 {
			var value = strings.Join(values, " ")
			return cssKeyString(key, value)
		} else {
			return nil
		}
	}
	var top = cssMeasure(measure.Top)
	var right = cssMeasure(measure.Right)
	var bottom = cssMeasure(measure.Bottom)
	var left = cssMeasure(measure.Left)
	if top == "" { return collect() }
	values = append(values, top)
	if right == "" { return collect() }
	values = append(values, right)
	if bottom == "" { return collect() }
	values = append(values, bottom)
	if left == "" { return collect() }
	values = append(values, left)
	return collect()
}
func cssKeyMaybeOverflow(key string, overflow MaybeOverflow) cssKeyValues {
	const forceDefault = "auto"
	if overflow == nil {
		return cssKeyString("overflow", forceDefault)
	}
	{ var overflow, set = overflow()
	if !(set) {
		return cssKeyString("overflow", forceDefault)
	}
	if overflow.X == overflow.Y {
		var value = overflow.X
		return cssKeyString(key, value)
	} else {
		var x string
		if overflow.X != "" {
			x = overflow.X
		} else {
			x = "auto"
		}
		var y string
		if overflow.Y != "" {
			y = overflow.Y
		} else {
			y = "auto"
		}
		var value = fmt.Sprintf("%s %s", x, y)
		return cssKeyString(key, value)
	} }
}
func cssKeyBorderStyle(key string, style BorderStyle, width QuadMeasure) cssKeyValues {
	if width.Top.Set || width.Right.Set ||
		width.Bottom.Set || width.Left.Set {
		var value string
		if style.BorderKind != "" {
			value = style.BorderKind
		} else {
			value = "solid"
		}
		return cssKeyString(key, value)
	} else {
		return nil
	}
}
func cssBoxShadow(shadow BoxShadow, ctx context) string {
	var fallback0 = func(m Measure) string {
		if m.Set {
			return cssMeasure(m)
		} else {
			return "0"
		}
	}
	var value = fmt.Sprintf (
		"%s %s %s %s %s",
		fallback0(shadow.OffsetX),
		fallback0(shadow.OffsetY),
		fallback0(shadow.BlurRadius),
		fallback0(shadow.SpreadRadius),
		cssColor(shadow.Color, ctx),
	)
	if shadow.Inset {
		value = fmt.Sprintf("inset %s", value)
	}
	return value
}
func cssKeyBoxShadows(key string, shadows ([] BoxShadow), ctx context) cssKeyValues {
	var shadow_values = make([] string, len(shadows))
	for i, shadow := range shadows {
		shadow_values[i] = cssBoxShadow(shadow, ctx)
	}
	var value = strings.Join(shadow_values, ", ")
	return cssKeyString(key, value)
}
func cssBackground(background Background, ctx context) string {
	switch B := background.Content.(type) {
	case BackgroundColor:
		return cssColor(B.Color, ctx)
	case BackgroundSimpleGradient:
		var to string
		if B.Gradient.To != "" {
			to = B.Gradient.To
		} else {
			to = "bottom"
		}
		var start string
		if B.Gradient.Start.Set {
			start = cssColor(B.Gradient.Start, ctx)
		} else {
			start = "hsla(0, 0%, 0%, 0)"
		}
		var end string
		if B.Gradient.End.Set {
			end = cssColor(B.Gradient.End, ctx)
		} else {
			end = start
		}
		return fmt.Sprintf (
			"linear-gradient(to %s, %s, %s)",
			to, start, end,
		)
	default:
		panic("impossible branch")
	}
}
func cssKeyMaybeBackground(key string, background MaybeBackground, ctx context) cssKeyValues {
	if background == nil { return nil }
	{ var background, set = background()
	if set {
		return cssKeyString(key, cssBackground(background, ctx))
	} else {
		return nil
	} }
}
func cssGridArea(area GridArea) string {
	return fmt.Sprintf (
		"%.f / %.f / %.f / %.f",
		area.StartRow, area.StartColumn,
		area.EndRow, area.EndColumn,
	)
}
func cssKeyMaybeGridArea(key string, area MaybeGridArea) cssKeyValues {
	if area == nil { return nil }
	{ var area, set = area()
	if set {
		return cssKeyString(key, cssGridArea(area))
	} else {
		return nil
	} }
}
func cssKeyGridTemplateSeq(key string, seq ([] Measure)) cssKeyValues {
	if len(seq) > 0 {
		var seq_val = make([] string, len(seq))
		for i, item := range seq {
			var item_val string
			if item.Set {
				item_val = cssMeasure(item)
			} else {
				item_val = "1fr"
			}
			seq_val[i] = item_val
		}
		var value = strings.Join(seq_val, " ")
		return cssKeyString(key, value)
	} else {
		return nil
	}
}
func cssKeyQuotedStringSeq(key string, seq ([] string)) cssKeyValues {
	if len(seq) > 0 {
		var seq_quoted = make([] string, len(seq))
		for i, item := range seq {
			seq_quoted[i] = strconv.Quote(item)
		}
		var value = strings.Join(seq_quoted, ", ")
		return cssKeyString(key, value)
	} else {
		return nil
	}
}


